/*
 * Created on 25. aug.. 2006
 * Created by Tomas Ekeli
 */
package people;

import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import util.Debug;

/**
 * 
 * The MembershipManager handles the relationships between Persons and Groups. 
 * A Person can be a member of many Groups, and a Group can have many Persons
 * as members. The MembershipManager also includes convenience-methods for 
 * manipulating all members of a Group.
 * 
 * 
 * @author Tomas Ekeli
 *
 */
public class MembershipManager implements Serializable{
	private static final long serialVersionUID = 1L;
	
	private LinkedList<Person> liPersons = 
		new LinkedList<Person>();
	private LinkedList<Group> liGroups = 
		new LinkedList<Group>();

	/**
	 * Constructs a new MembershipManager that can handle Persons and Groups.
	 *
	 */
	public MembershipManager() {
		liPersons = new LinkedList<Person>();
		liGroups = new LinkedList<Group>();
	}
	
	/**
	 * Add a Person to be managed
	 * @param p The Person to add
	 * @throws DuplicatePersonException if the Person could not be added. 
	 * Likely the p had the same uniquely identifying number as some existing
	 * Person.
	 */
	public void addPerson(Person p) throws DuplicatePersonException {
		
		if (Debug.ON) {
			System.out.print(p.getName() + "("+p.getScore()+") is in queue: ");
		}
		
		if (!liPersons.contains(p)) {
			if (Debug.ON) {
				System.out.println(liPersons.contains(p));
				
				System.out.println("Adding person " + p.getName() + "(" + p.getScore() +")");
			}
			liPersons.add(p);
			
			if (Debug.ON) {
				System.out.println("Queue is now: ");
				printQueue();
				
			}
		}
		else
			throw new DuplicatePersonException("Person " + p.getName() + " cannot be added. " 
					+ "Duplicate or same id as other person.");
		
	
	}

	public void removePerson(int number) throws PersonNotFoundException {
		try {
			Person removeMe = findPersonByID(number);
			liPersons.remove(removeMe);
		}
		catch (PersonNotFoundException e) {
			throw new PersonNotFoundException("Could not REMOVE " + number 
					+ ": not found. \n" + e.getMessage());
		}
	}
	
	public void removePerson(Person p) throws PersonNotFoundException {
		if (liPersons.contains(p)) {
			liPersons.remove(p);
			try {
				removePersonFromAllGroups(p);
			} catch (PersonNotFoundException e) {
				//the person was not part of a group, that's OK.
			}			
		}
		else {
			throw new PersonNotFoundException("Tried to remove " + p.getName() 
					+", not managed!");
		}
	}

	/**
	 * Add a Group to be Managed.
	 * @param g The Group
	 */
	public void addGroup(Group g) {
		//if the group is new, iterate and add score to Persons
		if (!liGroups.contains(g)) {
			if (Debug.ON) {
				System.out.println("---------------------------\nAdding group " +
						g.getName() +" (" + g.getScore() +")\n---------------------------");
			}
			
			liGroups.add(g);
			ratePersonsInGroup(g); 		//O(n)
		}
	}
	
	public int getNumberOfPersons() {
		return liPersons.size();
	}
	
	protected int getNumberOfGroups() {
		return liGroups.size();
	}
	
	/**
	 * @return An list of the Groups being managed
	 */
	public List<Group> getGroups() {
		return new LinkedList<Group>(liGroups);
	}
	
	/**
	 * Returns an ordered list of the Persons being managed by the
	 * MembershipManager. The list is ordered according to the Score of the
	 * Persons on the list at the moment this method is run.
	 * 
	 * @return An ordered List of the Persons being managed
	 */
	public List<Person> getPersons() {
		if (Debug.ON) {
			if (!liPersons.isEmpty()) {
				System.out.println("Returning queue of persons:");
				printQueue();
			}
		}
		LinkedList<Person> returnMe = new LinkedList<Person>(liPersons);
		Collections.sort(returnMe);
		
		return returnMe;
		
	}
	

	/**
	 * @param number The identifying number of the person to look for
	 * @return The Person with <code>number</code> as ID
	 * @throws PersonNotFoundException iff no person with the ID was found
	 */
	public Person getPerson(int number) throws PersonNotFoundException {
		for (Iterator<Person> iter = liPersons.iterator(); iter.hasNext();) {
			Person p = iter.next();
			if (p.getID() == number) {
				return p;
			}
		}
		throw new PersonNotFoundException("Could not GET person " + number 
				+ ": not found");
	}

	/**
	 * Adds the Person to the Group. If the Person or Group are not currently
	 * managed by the MemberShipManager they will be added to it.
	 * @param p The Person to add to the Group
	 * @param g The Group to add the Person to.
	 */
	public void addPersonToGroup(Person p, Group g) {
		if (!liGroups.contains(g)) {
			addGroup(g);
		}
		
		g.addPerson(p);
		
		if (!liPersons.contains(p)) {
			try {
				addPerson(p);
			} catch (DuplicatePersonException e) {
				// impossible, just checked that it wasn't there.
			}
		}
	}
	/**
	 * Removes the Person p from the Group g. If p was not a member of g nothing
	 * will be changed.
	 * 
	 * @param p The Person to remove
	 * @param g The Group to remove p from
	 */
	public void removePersonFromGroup(Person p, Group g) {
		try {
			g.removePerson(p);
		} catch (PersonNotFoundException e) {
			// excellent, wasn't a member
		}
	}
	
	/**
	 * Removes the Person from every Group known. An exception is raised if none
	 * of the known Groups contained the Person.
	 * @param p The Person to remove from all Groups
	 * @throws PersonNotFoundException iff the Person was not a member of any 
	 * Group (ie. the method did nothing).
	 */
	public void removePersonFromAllGroups(Person p) throws PersonNotFoundException {
		boolean bSuccess = false;
		for (Group g: liGroups) {
			removePersonFromGroup(p, g);
			bSuccess = true; 				//removed from at least one group
		}
		if (!bSuccess) {
			throw new PersonNotFoundException("Tried to remove " + p.getID() +
					" from all groups. Not found in any groups!");
		}
		
	}

	/**
	 * Clears all scores on all Persons and rates them anew.
	 *
	 */
	public void reRatePersons() {
		
		if (Debug.ON) {
			System.out.println("\nRe-sorting current queue :");
			printQueue();
			System.out.println();
		}
		
		//remove all scores
		for (Person clearMe : liPersons) {
			clearMe.setScore(0);		//O(n)
		}
		
		//go through all groups
		for (Group g : liGroups) {		//O(n^2)
			if (Debug.ON) {
				System.out.println("Scoring Persons in group : " + g.getName());
			}
			ratePersonsInGroup(g);		
		}
		
		if (Debug.ON) {
			System.out.println("New queue:");
			printQueue();
			System.out.println();
		}
		
	}

	/**
	 * Adds the score of the group to all the members of the group. If any member
	 * of the Group is unknown to the MemberShipManager they will be added to it.
	 * @param g The Group to iterate over
	 */
	private void ratePersonsInGroup(Group g) {
		for (Person p : g.getPersons()) {

			
			if (liPersons.contains(p)) {
				if (Debug.ON) {
					System.out.println("Updating score of " + p.getName());
				}
			}
			else { // If the Person was unknown, clear his score and add him
				p.setScore(0);
				try {
					addPerson(p);
				} catch (DuplicatePersonException e) {
					// impossible, we just checked that!
					e.printStackTrace();
				}
			}
			

			//Add this group's score
			p.addToScore(g.getScore());
			
			/*
			 * Sorting of the list in not needed here, although one might think
			 * it. The sorting is handled exclusively in the methods that return
			 * People, as the list need not be sorted except then. I hope.
			 */
			
			
			if (Debug.ON) {
				System.out.println("NEW SUM = " + p.getScore());
				System.out.println("\nQueue right now: ");
				printQueue();
				System.out.println();
			}
			
		}
		
	}
	
	/**
	 * Searches the known Persons for one with the given ID. The first
	 * occurence will be returned.
	 * @param id The ID to search for.
	 * @return The Person with the given ID
	 * @throws PersonNotFoundException iff no person with the ID is known.
	 */
	private Person findPersonByID(int id) throws PersonNotFoundException {
		for (Person current : liPersons) {
			if(current.getID() == id) {
				return current;
			}
		}

		throw new PersonNotFoundException("Could not find a person with id: " + id);
	}
		

	private void printQueue() {
		if (liPersons.isEmpty()) {
			System.out.println("[Empty]");
			return;
		}
			
		
		for (Person p : liPersons) {
			System.out.println(p.getScore() + ": " + p.getName());
		}
		System.out.println();
	}

}
